﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using LINQPad.Extensibility.DataContext;
using System.Windows.Forms;
using System.CodeDom.Compiler;
using System.Diagnostics;
using Microsoft.SharePoint.Utilities;

using Microsoft.CSharp;

namespace LinqPadSP2010ContextDriver.DynamicDriver
{
	/// <summary>
	/// This static driver let users query any data source that looks like a Data Context - in other words,
	/// that exposes properties of type IEnumerable of T.
	/// </summary>
	public class SharePointDriver : DynamicDataContextDriver
	{
		public override string Name { get { return "Dynamic SharePoint 2010 Driver"; } }

		public override string Author { get { return "Marcus Peters"; } }	

		public override string GetConnectionDescription (IConnectionInfo cxInfo)
		{
			return cxInfo.DriverData.Element("SPUrl").Value;
		}

        public override object[] GetContextConstructorArguments(IConnectionInfo cxInfo)
        {
            return new object[] {cxInfo.DriverData.Element("SPUrl").Value};                  
        }

        public override ParameterDescriptor[] GetContextConstructorParameters(IConnectionInfo cxInfo)
        {
            return new ParameterDescriptor[] { new ParameterDescriptor("requestUrl", "System.String") };
        }
        
        
        public override bool ShowConnectionDialog (IConnectionInfo cxInfo, bool isNewConnection)
		{
			// Prompt the user for a custom assembly and type name:
			return new ConnectionDialog (cxInfo).ShowDialog() == true;
		}

        public override void InitializeContext(IConnectionInfo cxInfo, object context, QueryExecutionManager executionManager)
        {
            ((Microsoft.SharePoint.Linq.DataContext)context).Log = executionManager.SqlTranslationWriter;
        }
        

        public override IEnumerable<string> GetAssembliesToAdd()
        {
            string spdllHive = SPUtility.GetGenericSetupPath("ISAPI");         
            return new[] {Path.Combine(spdllHive, "Microsoft.SharePoint.dll"),Path.Combine(spdllHive, "Microsoft.SharePoint.Linq.dll")};
        }


        public override void TearDownContext(IConnectionInfo cxInfo, object context, QueryExecutionManager executionManager, object[] constructorArguments)
        {
            ((IDisposable)context).Dispose(); 
            
        }


        #region Get the Schema


        /// <summary>
        /// This is taken from the dynamic driver code 
        /// </summary>
        /// <param name="cxInfo"></param>
        /// <param name="customType"></param>
        /// <returns></returns>
        internal List<ExplorerItem> GetSchema(IConnectionInfo cxInfo, Type customType)
        {
            // Return the objects with which to populate the Schema Explorer by reflecting over customType.

            // We'll start by retrieving all the properties of the custom type that implement IEnumerable<T>:
            var topLevelProps =
            (
                from prop in customType.GetProperties()
                //where prop.PropertyType != typeof (string)
                where prop.PropertyType.ToString().StartsWith("Microsoft.SharePoint.Linq.EntityList`1")


                // Display all properties of type IEnumerable<T> which are of EntitiList Type
                let ienumerableOfT = prop.PropertyType.GetInterface("System.Collections.Generic.IEnumerable`1")
                where (ienumerableOfT != null)

                orderby prop.Name

                select new ExplorerItem(prop.Name, ExplorerItemKind.QueryableObject, ExplorerIcon.Table)
                {
                    IsEnumerable = true,
                    ToolTipText = FormatTypeName(prop.PropertyType, false),

                    // Store the entity type to the Tag property. We'll use it later.
                    Tag = ienumerableOfT.GetGenericArguments()[0]

                }

            ).ToList();

            // Create a lookup keying each element type to the properties of that type. This will allow
            // us to build hyperlink targets allowing the user to click between associations:
            var elementTypeLookup = topLevelProps.ToLookup(tp => (Type)tp.Tag);

            // Populate the columns (properties) of each entity:
            foreach (ExplorerItem table in topLevelProps)
                table.Children = ((Type)table.Tag)
                    .GetProperties()
                    .Select(childProp => GetChildItem(elementTypeLookup, childProp))
                    .OrderBy(childItem => childItem.Kind)
                    .ToList();

            return topLevelProps;
        }

        
        
        
        ExplorerItem GetChildItem (ILookup<Type, ExplorerItem> elementTypeLookup, PropertyInfo childProp)
		{
			// If the property's type is in our list of entities, then it's a Many:1 (or 1:1) reference.
			// We'll assume it's a Many:1 (we can't reliably identify 1:1s purely from reflection).
			if (elementTypeLookup.Contains (childProp.PropertyType))
				return new ExplorerItem (childProp.Name, ExplorerItemKind.ReferenceLink, ExplorerIcon.ManyToOne)
				{
					HyperlinkTarget = elementTypeLookup [childProp.PropertyType].First (),
					// FormatTypeName is a helper method that returns a nicely formatted type name.
					ToolTipText = FormatTypeName (childProp.PropertyType, true)
				};

			// Is the property's type a collection of entities?
			Type ienumerableOfT = childProp.PropertyType.GetInterface ("System.Collections.Generic.IEnumerable`1");
			if (ienumerableOfT != null)
			{
				Type elementType = ienumerableOfT.GetGenericArguments () [0];
				if (elementTypeLookup.Contains (elementType))
					return new ExplorerItem (childProp.Name, ExplorerItemKind.CollectionLink, ExplorerIcon.OneToMany)
					{
						HyperlinkTarget = elementTypeLookup [elementType].First (),
						ToolTipText = FormatTypeName (elementType, true)
					};
			}

			// Ordinary property:
			return new ExplorerItem (childProp.Name + " (" + FormatTypeName (childProp.PropertyType, false) + ")",
				ExplorerItemKind.Property, ExplorerIcon.Column);
		}

#endregion

        #region runspmetal
        internal void runspmetal(string arguments, bool viewoutput)
        {
            string spmetalpath = Path.Combine(SPUtility.GetGenericSetupPath("BIN"), "spmetal.exe");



            Process p = new Process();
            string output = null;

            try
            {
                p.StartInfo.UseShellExecute = false;
                p.StartInfo.CreateNoWindow = true;
                p.StartInfo.Arguments = arguments;
                p.StartInfo.WorkingDirectory = this.GetDriverFolder();
                p.StartInfo.FileName = spmetalpath;
                p.StartInfo.RedirectStandardOutput = true;
                p.Start();
                
                output = p.StandardOutput.ReadToEnd();
                p.WaitForExit();

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                if (viewoutput)
                    MessageBox.Show(output,"SPMetal Code Generation Result:");

            }
        }
        #endregion


        public override List<ExplorerItem> GetSchemaAndBuildAssembly(IConnectionInfo cxInfo, AssemblyName assemblyToBuild, ref string nameSpace, ref string typeName)
        {
            //run spmetal
            string spContextType = "SiteEntities";
            string codeNameSpace = "SharePointDriver";
            string codeFile = string.Format("{0}.cs",spContextType);
            string args = string.Format("/web:{0} /code:{1} /namespace:{2}", cxInfo.DriverData.Element("SPUrl").Value, codeFile, codeNameSpace);

            bool parseResult = false;
            bool viewOutput = bool.TryParse(cxInfo.DriverData.Element("ViewSPMetalOutput").Value,out parseResult);
            
                      
            this.runspmetal(args, parseResult ? viewOutput : false);

            //compile the code
            string code; 
            using (TextReader tr = new StreamReader(Path.Combine(this.GetDriverFolder(), codeFile)))
            {
                code = tr.ReadToEnd();
                tr.Close();
            }

            BuildAssembly(code, assemblyToBuild);
            nameSpace = codeNameSpace;
            typeName = string.Format("{0}DataContext", spContextType);
            Assembly a = AppDomain.CurrentDomain.Load(assemblyToBuild);
            
            return this.GetSchema(cxInfo, a.GetType(string.Format("{0}.{1}",nameSpace,typeName)));
        }

        static void BuildAssembly(string code, AssemblyName name)
        {
            // Use the CSharpCodeProvider to compile the generated code:
            CompilerResults results;
            using (var codeProvider = new CSharpCodeProvider(new Dictionary<string, string>() { { "CompilerVersion", "v3.5" } }))
            {
                var options = new CompilerParameters(
                    "System.dll System.Core.dll System.Xml.dll System.Data.Services.Client.dll".Split(),
                    name.CodeBase,
                    true);
                               
                options.ReferencedAssemblies.Add(Path.Combine(SPUtility.GetGenericSetupPath("ISAPI"),"Microsoft.SharePoint.dll"));
                options.ReferencedAssemblies.Add(Path.Combine(SPUtility.GetGenericSetupPath("ISAPI"), "Microsoft.SharePoint.Linq.dll"));

                results = codeProvider.CompileAssemblyFromSource(options, code);
            }
            if (results.Errors.Count > 0)
                throw new Exception
                    ("Cannot compile typed context: " + results.Errors[0].ErrorText + " (line " + results.Errors[0].Line + ")");
        }
	}
}
